home *** CD-ROM | disk | FTP | other *** search
/ Delphi 5 for Professionals / DELPHI5.iso / AddOns / Delphi 5 Companion Tools CD / FreeWare / HVDLL / HVDLL.ZIP / HVDll.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1999-08-04  |  14.5 KB  |  550 lines

  1. unit HVDll;
  2. //
  3. // Support for DelayLoading of DLLs ß la VC++6.0
  4. // Written by Hallvard Vassbotn (hallvard@balder.no), January 1999
  5. // Documentation in The Delphi Magazine, March 1999 issue
  6. //
  7. interface
  8.  
  9. uses
  10.   Windows,
  11.   Classes,
  12.   SysUtils;
  13.  
  14. //
  15. // Simple wrapper classes around the Win32 Heap functions.
  16. // Moved in from the HVHeaps unit
  17. //
  18. type
  19.   // The TPrivateHeap class gives basic memory allocation capability
  20.   // The benefit of using this class instead of the native GetMem
  21.   // and FreeMem routines, is that the memory pages used will
  22.   // be seperate from other allocations. This gives reduced
  23.   // fragmentation.
  24.   TPrivateHeap = class(TObject)
  25.   private
  26.     FHandle: THandle;
  27.     FAllocationFlags: DWORD;
  28.     function GetHandle: THandle;
  29.   public
  30.     destructor Destroy; override;
  31.     procedure GetMem(var P{: pointer}; Size: DWORD); virtual;
  32.     procedure FreeMem(P: pointer);
  33.     function SizeOfMem(P: pointer): DWORD;
  34.     property Handle: THandle read GetHandle;
  35.     property AllocationFlags: DWORD read FAllocationFlags write FAllocationFlags;
  36.   end;
  37.  
  38.   // The Code Heap adds the feature of allocating readable/writable
  39.   // and executable memory blocks. This allows us to have safe
  40.   // run-time generated code while not wasting as much memory
  41.   // as calls to VirtualAlloc would have caused, while avoiding
  42.   // the pitfalls of changing the protection flags of blocks
  43.   // allocated with GetMem.
  44.   TCodeHeap = class(TPrivateHeap)
  45.   public
  46.     procedure GetMem(var P{: pointer}; Size: DWORD); override;
  47.   end;
  48.  
  49. type
  50.   // Structures to keep the address of function variables and name/id pairs
  51.   PPointer = ^pointer;
  52.   PEntry = ^TEntry;
  53.   TEntry = packed record
  54.     Proc: PPointer;
  55.     case integer of
  56.       0 : (Name: PChar);
  57.       1 : (ID  : Longint);
  58.     end;
  59.   PEntries = ^TEntries;
  60.   TEntries = packed array[0..High(Word)-1] of TEntry;
  61.  
  62.   // Structures to generate the per-routine thunks
  63.   PThunk = ^TThunk;
  64.   TThunk = packed record
  65.     CALL  : byte;
  66.     OFFSET: integer;
  67.   end;
  68.   PThunks = ^TThunks;
  69.   TThunks = packed array[0..High(Word)-1] of TThunk;
  70.  
  71.   // Structure to generate the per-DLL thunks
  72.   TThunkHeader = packed record
  73.     PUSH   : byte;
  74.     VALUE  : pointer;
  75.     JMP    : byte;
  76.     OFFSET : integer;
  77.   end;
  78.  
  79.   // The combined per-DLL and per-routine thunks
  80.   PThunkingCode = ^TThunkingCode;
  81.   TThunkingCode = packed record
  82.     ThunkHeader : TThunkHeader;
  83.     Thunks      : TThunks;
  84.   end;
  85.  
  86.   // The base class that provides DelayLoad capability
  87.   TDll = class(TObject)
  88.   private
  89.     FEntries  : PEntries;
  90.     FThunkingCode: PThunkingCode;
  91.     FCount    : integer;
  92.     FFullPath : string;
  93.     FHandle   : HMODULE;
  94.     function GetHandle: HMODULE;
  95.     procedure SetFullPath(const Value: string);
  96.     function GetProcs(Index: integer): pointer;
  97.     procedure SetProcs(Index: integer; Value: pointer);
  98.     function GetAvailable: boolean;
  99.     function GetLoaded: boolean;
  100.     function LoadProcAddrFromIndex(Index: integer; var Addr: pointer): boolean;
  101.     procedure ActivateThunks;
  102.     function GetEntryName(Index: integer): string;
  103.   protected
  104.     function LoadHandle: HMODULE; virtual;
  105.     class procedure Error(const Msg: string; Args: array of const);
  106.     procedure CreateThunks;
  107.     procedure DestroyThunks;
  108.     function HasThunk(Thunk: PThunk): boolean;
  109.     function GetProcAddrFromIndex(Index: integer): pointer;
  110.     function DelayLoadFromThunk(Thunk: PThunk): pointer; register;
  111.     function DelayLoadIndex(Index: integer): pointer;
  112.     function GetIndexFromThunk(Thunk: PThunk): integer;
  113.     function GetIndexFromProc(Proc: PPointer): integer;
  114.     function ValidIndex(Index: integer): boolean;
  115.     procedure CheckIndex(Index: integer);
  116.     property Procs[Index: integer]: pointer read GetProcs write SetProcs;
  117.   public
  118.     constructor Create(const DllName: string; const Entries: array of TEntry);
  119.     destructor Destroy; override;
  120.     procedure Load;
  121.     procedure Unload;
  122.     function HasRoutine(Proc: PPointer): boolean;
  123.     function HookRoutine(Proc: PPointer; HookProc: Pointer; var OrgProc{: Pointer}): boolean;
  124.     function UnHookRoutine(Proc: PPointer; var OrgProc{: Pointer}): boolean;
  125.     property FullPath: string read FFullPath write SetFullPath;
  126.     property Handle: HMODULE read GetHandle;
  127.     property Loaded: boolean read GetLoaded;
  128.     property Available: boolean read GetAvailable;
  129.     property Count: integer read FCount;
  130.     property EntryName[Index: integer]: string read GetEntryName;
  131.   end;
  132.  
  133.   // The class that keeps a list of all created TDll instances in one place
  134.   TDllNotifyAction = (daLoadedDll, daUnloadedDll, daLinkedRoutine);
  135.   TDllNotifyEvent = procedure(Sender: TDll; Action: TDllNotifyAction; Index: integer) of object;
  136.   TDlls = class(TList)
  137.   private
  138.     FCodeHeap: TCodeHeap;
  139.     FOnDllNotify: TDllNotifyEvent;
  140.     function GetDlls(Index: integer): TDll;
  141.   protected
  142.     procedure DllNotify(Sender: TDll; Action: TDllNotifyAction; Index: integer);
  143.     property CodeHeap: TCodeHeap read FCodeHeap;
  144.   public
  145.     constructor Create;
  146.     destructor Destroy; override;
  147.     property Dlls[Index: integer]: TDll read GetDlls; default;
  148.     property OnDllNotify: TDllNotifyEvent read FOnDllNotify write FOnDllNotify;
  149.   end;
  150.  
  151.   EDllError = class(Exception);
  152.  
  153. var
  154.   Dlls: TDlls;
  155.  
  156. implementation
  157.  
  158. function Win32Handle(Handle: THandle): THandle;
  159. begin
  160.   if Handle = 0 then
  161.     RaiseLastWin32Error;
  162.   Result := Handle;
  163. end;
  164.  
  165. function Win32Pointer(P: Pointer): Pointer;
  166. begin
  167.   if P = nil then
  168.     RaiseLastWin32Error;
  169.   Result := P;
  170. end;
  171.  
  172. { TPrivateHeap }
  173.  
  174. destructor TPrivateHeap.Destroy;
  175. begin
  176.   if FHandle <> 0 then
  177.   begin
  178.     Win32Check(Windows.HeapDestroy(FHandle));
  179.     FHandle := 0;
  180.   end;
  181.   inherited Destroy;
  182. end;
  183.  
  184. procedure TPrivateHeap.FreeMem(P: pointer);
  185. begin
  186.   Win32Check(Windows.HeapFree(Handle, 0, P));
  187. end;
  188.  
  189. function TPrivateHeap.GetHandle: THandle;
  190. begin
  191.   if FHandle = 0 then
  192.     FHandle := Win32Handle(Windows.HeapCreate(0, 0, 0));
  193.   Result := FHandle;
  194. end;
  195.  
  196. procedure TPrivateHeap.GetMem(var P{: pointer}; Size: DWORD);
  197. begin
  198.   Pointer(P) := Win32Pointer(Windows.HeapAlloc(Handle, AllocationFlags, Size));
  199. end;
  200.  
  201. function TPrivateHeap.SizeOfMem(P: pointer): DWORD;
  202. begin
  203.   Result := Windows.HeapSize(Handle, 0, P);
  204.   // HeapSize does not set GetLastError, but returns $FFFFFFFF if it fails
  205.   if Result = $FFFFFFFF then
  206.     Result := 0;
  207. end;
  208.  
  209. { TCodeHeap }
  210.  
  211. procedure TCodeHeap.GetMem(var P{: pointer}; Size: DWORD);
  212. var
  213.   Dummy: DWORD;
  214. begin
  215.   inherited GetMem(P, Size);
  216.   Win32Check(Windows.VirtualProtect(Pointer(P), Size, PAGE_EXECUTE_READWRITE, @Dummy));
  217. end;
  218.  
  219. resourcestring
  220.   SIndexOutOfRange      = 'DLL-entry index out of range (%d)';
  221.   SOrdinal              = 'ordinal #';
  222.   SCannotLoadLibrary    = 'Could not find the library: "%s"'#13#10'(%s)';
  223.   SCannotGetProcAddress = 'Could not find the routine "%s" in the library "%s"'#13#10'(%s)';
  224.   SCannotFindThunk      = 'Could not find the TDll object corresponding to the thunk address %p';
  225.  
  226. { Helper routines }
  227.  
  228. function EntryToString(const Entry: TEntry): string;
  229. begin
  230.   if Hi(Entry.ID) <> 0
  231.   then Result := string(Entry.Name)
  232.   else Result := SOrdinal+IntToStr(Entry.ID);
  233. end;
  234.  
  235. procedure ThunkingTarget;
  236. const
  237.   TThunkSize = SizeOf(TThunk);
  238. asm
  239.   // Save register-based parameters
  240.   PUSH    EAX
  241.   PUSH    EDX
  242.   PUSH    ECX
  243. { Stack layout at this point:
  244.   24 [Stack based parameters]
  245.   20 [User code RetAdr]
  246.   16 [Thunk Ret-Adr]
  247.   12 [Self]
  248.    8 [EAX]
  249.    4 [EDX]
  250.    0 [ECX] <-ESP}
  251.   // Get the caller's return address (i.e. one of the thunks)
  252.   MOV     EAX, [ESP+12]   // Self
  253.   MOV     EDX, [ESP+16]   // Thunk
  254.   // The return address is just after the thunk that
  255.   // called us, so go back one step
  256.   SUB     EDX, TYPE TThunk // Using SizeOf(TThunk) here does not work. BASM bug?
  257.   // Do the rest in Pascal
  258.   CALL    TDll.DelayLoadFromThunk{(Self, Thunk);}
  259.   // Now patch the return address on the stack so that we "return" to the DLL routine
  260.   MOV     [ESP+16], EAX
  261.   // Restore register-based parameters
  262.   POP     ECX
  263.   POP     EDX
  264.   POP     EAX
  265.   // Remove the Self pointer!
  266.   ADD        ESP,  4
  267.   // "RETurn" to the DLL!
  268. end;
  269.  
  270. { TDll }
  271.  
  272. constructor TDll.Create(const DllName: string; const Entries: array of TEntry);
  273. begin
  274.   inherited Create;
  275.   FFullPath := DllName;
  276.   FEntries  := @Entries;
  277.   FCount    := High(Entries) - Low(Entries) + 1;
  278.   CreateThunks;
  279.   ActivateThunks;
  280.   Dlls.Add(Self);
  281. end;
  282.  
  283. destructor TDll.Destroy;
  284. begin
  285.   Dlls.Remove(Self);
  286.   Unload;
  287.   DestroyThunks;
  288.   inherited Destroy;
  289. end;
  290.  
  291. procedure TDll.CreateThunks;
  292. const
  293.   CallInstruction = $E8;
  294.   PushInstruction = $68;
  295.   JumpInstruction = $E9;
  296. var
  297.   i : integer;
  298. begin
  299.   // Get a memory block large enough for the thunks
  300.   Dlls.CodeHeap.GetMem(FThunkingCode, SizeOf(TThunkHeader) + SizeOf(TThunk) * Count);
  301.  
  302.   // Generate some machine code in the thunks
  303.   with FThunkingCode^, ThunkHeader do
  304.   begin
  305.     // The per-Dll thunk does this:
  306.     // PUSH    Self
  307.     // JMP     ThunkingTarget
  308.     PUSH   := PushInstruction;
  309.     VALUE  := Self;
  310.     JMP    := JumpInstruction;
  311.     OFFSET := PChar(@ThunkingTarget) - PChar(@Thunks[0]);
  312.     for i := 0 to Count-1 do
  313.       with Thunks[i] do
  314.       begin
  315.         // The per-entry thunk does this:
  316.         // CALL @ThunkingCode^.ThunkHeader
  317.         CALL   := CallInstruction;
  318.         OFFSET := PChar(@FThunkingCode^.ThunkHeader) - PChar(@Thunks[i+1]);
  319.       end;
  320.   end;
  321. end;
  322.  
  323. procedure TDll.DestroyThunks;
  324. begin
  325.   if Assigned(FThunkingCode) then
  326.   begin
  327.     Dlls.CodeHeap.FreeMem(FThunkingCode);
  328.     FThunkingCode := nil;
  329.   end;
  330. end;
  331.  
  332. function TDll.DelayLoadFromThunk(Thunk: PThunk): pointer; register;
  333. begin
  334.   Result := DelayLoadIndex(GetIndexFromThunk(Thunk));
  335. end;
  336.  
  337. function TDll.DelayLoadIndex(Index: integer): pointer;
  338. begin
  339.   Result := GetProcAddrFromIndex(Index);
  340.   FEntries^[Index].Proc^ := Result;
  341. end;
  342.  
  343. class procedure TDll.Error(const Msg: string; Args: array of const);
  344. begin
  345.   raise EDllError.CreateFmt(Msg, Args);
  346. end;
  347.  
  348. function TDll.LoadHandle: HMODULE;
  349. begin
  350.   if FHandle = 0 then
  351.   begin
  352.     FHandle := Windows.LoadLibrary(PChar(FullPath));
  353.     if FHandle <> 0 then
  354.       Dlls.DllNotify(Self, daLoadedDll, -1);
  355.   end;
  356.   Result := FHandle;
  357. end;
  358.  
  359. function TDll.GetHandle: HMODULE;
  360. begin
  361.   Result := FHandle;
  362.   if Result = 0 then
  363.   begin
  364.     Result := LoadHandle;
  365.     if Result = 0 then
  366.       Error(SCannotLoadLibrary, [FullPath, SysErrorMessage(GetLastError)]);
  367.   end;
  368. end;
  369.  
  370. function TDll.GetIndexFromThunk(Thunk: PThunk): integer;
  371. begin
  372.   // We calculate the thunk index by subtracting the start of the array
  373.   // and dividing by the size of the array elements
  374.   Result := (PChar(Thunk) - PChar(@FThunkingCode^.Thunks[0])) div SizeOf(TThunk);
  375. end;
  376.  
  377. function TDll.LoadProcAddrFromIndex(Index: integer; var Addr: pointer): boolean;
  378. begin
  379.   Result := ValidIndex(Index);
  380.   if Result then
  381.   begin
  382.     Addr := Windows.GetProcAddress(Handle, FEntries^[Index].Name);
  383.     Result := Assigned(Addr);
  384.     if Result then
  385.       Dlls.DllNotify(Self, daLinkedRoutine, Index);
  386.   end;
  387. end;
  388.  
  389. function TDll.GetProcAddrFromIndex(Index: integer): pointer;
  390. begin
  391.   if not LoadProcAddrFromIndex(Index, Result) then
  392.     Error(SCannotGetProcAddress, [EntryName[Index], FullPath, SysErrorMessage(GetLastError)]);
  393. end;
  394.  
  395. function TDll.HasThunk(Thunk: PThunk): boolean;
  396. begin
  397.   // The thunk belongs to us if its address is in the thunk array
  398.   Result := (PChar(Thunk) >= PChar(@FThunkingCode^.Thunks[0])) and
  399.             (PChar(Thunk) <= PChar(@FThunkingCode^.Thunks[Count-1]));
  400. end;
  401.  
  402. procedure TDll.Load;
  403. var
  404.   i : integer;
  405. begin
  406.   for i := 0 to Count-1 do
  407.     DelayLoadIndex(i);
  408. end;
  409.  
  410. procedure TDll.SetFullPath(const Value: string);
  411. begin
  412.   if CompareText(FFullPath, Value) <> 0 then
  413.   begin
  414.     Unload;
  415.     FFullPath := Value;
  416.   end;
  417. end;
  418.  
  419. function TDll.GetEntryName(Index: integer): string;
  420. begin
  421.   if ValidIndex(Index)
  422.   then Result := EntryToString(FEntries^[Index])
  423.   else Result := Format(SIndexOutOfRange, [Index]);
  424. end;
  425.  
  426. procedure TDll.ActivateThunks;
  427. // Patch the procedure variables to point to the generated thunks
  428. var
  429.   i : integer;
  430. begin
  431.   for i := 0 to Count-1 do
  432.     FEntries^[i].Proc^ := @FThunkingCode^.Thunks[i];
  433. end;
  434.  
  435. procedure TDll.Unload;
  436. begin
  437.   ActivateThunks;
  438.   if FHandle <> 0 then
  439.   begin
  440.     FreeLibrary(FHandle);
  441.     Dlls.DllNotify(Self, daUnloadedDll, -1);
  442.     FHandle := 0;
  443.   end;
  444. end;
  445.  
  446. function TDll.ValidIndex(Index: integer): boolean;
  447. begin
  448.   Result := (Index >= 0) and (Index <= Count-1);
  449. end;
  450.  
  451. procedure TDll.CheckIndex(Index: integer);
  452. begin
  453.   if not ValidIndex(Index) then
  454.     Error(SIndexOutOfRange, [Index]);
  455. end;
  456.  
  457. function TDll.GetProcs(Index: integer): pointer;
  458. begin
  459.   CheckIndex(Index);
  460.   Result := FEntries^[Index].Proc^;
  461. end;
  462.  
  463. procedure TDll.SetProcs(Index: integer; Value: pointer);
  464. begin
  465.   CheckIndex(Index);
  466.   FEntries^[Index].Proc^ := Value;
  467. end;
  468.  
  469. function TDll.GetAvailable: boolean;
  470. begin
  471.   Result := (LoadHandle <> 0);
  472. end;
  473.  
  474. function TDll.GetLoaded: boolean;
  475. begin
  476.   Result := (FHandle <> 0);
  477. end;
  478.  
  479. function TDll.GetIndexFromProc(Proc: PPointer): integer;
  480. begin
  481.   for Result := 0 to Count-1 do
  482.     if FEntries^[Result].Proc = Proc then
  483.       Exit;
  484.   Result := -1;
  485. end;
  486.  
  487. function TDll.HasRoutine(Proc: PPointer): boolean;
  488. begin
  489.   Result := Available and
  490.             ((not HasThunk(Proc^)) or
  491.               LoadProcAddrFromIndex(GetIndexFromProc(Proc), Proc^));
  492. end;
  493.  
  494. function TDll.HookRoutine(Proc: PPointer; HookProc: Pointer; var OrgProc{: Pointer}): boolean;
  495. begin
  496.   Result := HasRoutine(Proc);
  497.   if Result then
  498.   begin
  499.     Pointer(OrgProc) := Proc^;
  500.     Proc^   := HookProc;
  501.   end;
  502. end;
  503.  
  504. function TDll.UnHookRoutine(Proc: PPointer; var OrgProc{: Pointer}): boolean;
  505. begin
  506.   Result := Assigned(Pointer(OrgProc));
  507.   if Result then
  508.   begin
  509.     Proc^ := Pointer(OrgProc);
  510.     Pointer(OrgProc) := nil;
  511.   end;
  512. end;
  513.  
  514. { TDlls }
  515.  
  516. constructor TDlls.Create;
  517. begin
  518.   inherited Create;
  519.   FCodeHeap := TCodeHeap.Create;
  520. end;
  521.  
  522. destructor TDlls.Destroy;
  523. var
  524.   i : integer;
  525. begin
  526.   for i := Count-1 downto 0 do
  527.     Dlls[i].Free;
  528.   FCodeHeap.Free;
  529.   FCodeHeap := nil;
  530.   inherited Destroy;
  531. end;
  532.  
  533. procedure TDlls.DllNotify(Sender: TDll; Action: TDllNotifyAction; Index: integer);
  534. begin
  535.   if Assigned(FOnDllNotify) then
  536.     FOnDllNotify(Sender, Action, Index);
  537. end;
  538.  
  539. function TDlls.GetDlls(Index: integer): TDll;
  540. begin
  541.   Result := TDll(Items[Index]);
  542. end;
  543.  
  544. initialization
  545.   Dlls := TDlls.Create;
  546. finalization
  547.   Dlls.Free;
  548.   Dlls := nil;
  549. end.
  550.